/**
 * @module vim-loader
 */

import {
  Uint32BufferAttribute,
  BufferGeometry,
  Matrix4,
  InstancedMesh,
  BufferAttribute
} from 'three'
import { G3d, G3dMaterial, MeshSection } from 'vim-format'
import { MaterialsTool } from '../materials/MaterialsTool.ts'

export class InstancedMeshFactory {

  constructor(public materials: G3dMaterial) {
  }


  createOpaqueFromVim(g3d: G3d, mesh: number, instances: number[]) {
    return this.createFromVim(g3d, mesh, instances, 'opaque')
  }

  createTransparentFromVim(g3d: G3d, mesh: number, instances: number[]) {
    return this.createFromVim(g3d, mesh, instances, 'transparent')
  }



  createFromVim(g3d: G3d, mesh: number, instances: number[], section: MeshSection) {
    const transparent = section === 'transparent'

    const colorSize = transparent ? 4 : 3
    const colors = new Float32Array(g3d.getMeshVertexCount(mesh) * colorSize)

    const subStart = g3d.getMeshSubmeshStart(mesh)
    const subEnd = g3d.getMeshSubmeshEnd(mesh)

    for (let submesh = subStart; submesh < subEnd; submesh++) {
      const color = g3d.getSubmeshColor(submesh)
      const start = g3d.getSubmeshIndexStart(submesh)
      const end = g3d.getSubmeshIndexEnd(submesh)

      for (let i = start; i < end; i++) {
        const v = g3d.indices[i] * colorSize
        colors[v] = color[0]
        colors[v + 1] = color[1]
        colors[v + 2] = color[2]
        if (transparent) { colors[v + 3] = color[3] }
      }
    }


    const positions = g3d.positions.subarray(
      g3d.getMeshVertexStart(mesh) * 3,
      g3d.getMeshVertexEnd(mesh) * 3
    )

    const start = g3d.getMeshIndexStart(mesh, section)
    const end = g3d.getMeshIndexEnd(mesh, section)
    const indices = g3d.indices.subarray(start, end)
    const geometry = new BufferGeometry()

    // Vertices
    geometry.setAttribute('position', new BufferAttribute(positions, 3))

    // Indices
    geometry.setIndex(new Uint32BufferAttribute(indices, 1))

    // Colors with alpha if transparent
    if (colors) {
      geometry.setAttribute(
        'color',
        new BufferAttribute(colors, colorSize)
      )
    }


    const material = transparent
      ? MaterialsTool.getInstance().transparent.material
      : MaterialsTool.getInstance().opaque.material
    const threeMesh = new InstancedMesh(
      geometry,
      material,
      instances?.length ?? g3d.getMeshInstanceCount(mesh)
    )

    const matrix = new Matrix4()
    for (let i = 0; i < instances.length; i++) {
      const array = g3d.getInstanceMatrix(instances[i])
      matrix.fromArray(array)
      threeMesh.setMatrixAt(i, matrix)
    }
    // const result = new InstancedMesh(g3d, threeMesh, instances)
    return threeMesh
  }

}
